#include "zen_game_graphics_gles.h"
#if ZEN_OS_WIN
#include "./gles3/zen_egl.h"
#include "zen_app_windows.h"

static std::shared_ptr<Zen::GL::Window> sEGL;
#endif

namespace Zen
{
    namespace Graphics
    {
        static const Mat4 sUnitMatrix = Mat4MakeIdentity();
        static const Color4f sDefaultUColor = Color4f(0.f, 0.f, 0.f, 1.f);

        Zen::GL::EDrawMode ToGL(EDrawMode mode)
        {
            static Zen::GL::EDrawMode modes[] = {
                Zen::GL::EDrawMode::Point,
                Zen::GL::EDrawMode::Line,
                Zen::GL::EDrawMode::LineLoop,
                Zen::GL::EDrawMode::LineStrip,
                Zen::GL::EDrawMode::Triangle,
                Zen::GL::EDrawMode::TriangleStrip,
            };
            return modes[size_t(mode)];
        }

        Zen::GL::EFace ToGL(EFace face)
        {
            static Zen::GL::EFace faces[] = {
                Zen::GL::EFace::None,
                Zen::GL::EFace::Front,
                Zen::GL::EFace::Back,
                Zen::GL::EFace::Both,
            };
            return faces[size_t(face)];
        }

        Zen::GL::EMinMagFilter ToGL(EMinMagFilter filter)
        {
            static Zen::GL::EMinMagFilter filters[] = {
                Zen::GL::EMinMagFilter::Nearest,
                Zen::GL::EMinMagFilter::Linear,
            };
            return filters[size_t(filter)];
        }

        Zen::GL::EWrapMode ToGL(EWrapMode mode)
        {
            Zen::GL::EWrapMode modes[] = {
                Zen::GL::EWrapMode::ClampToEdge,
                Zen::GL::EWrapMode::MirrorRepeat,
                Zen::GL::EWrapMode::Repeat,
                Zen::GL::EWrapMode::MirrorRepeat,
            };
            return modes[size_t(mode)];
        }

        float TextureGLES::GetWidth() const
        {
            return mWidth;
        }
        float TextureGLES::GetHeight() const
        {
            return mHeight;
        }
        EPixel TextureGLES::GetPixelType() const
        {
            return mTexture.GetPixelType();
        }

        void TextureGLES::Generate(EPixel bpp, size_t width, size_t height, void const* data)
        {
            mTexture.Generate(bpp, width, height, data);
            mWidth = float(width);
            mHeight = float(height);
        }

        GLuint TextureGLES::GetTextureId() const
        {
            return mTexture.GetId();
        }

        std::shared_ptr<EngineGLES> EngineGLES::Only()
        {
            static auto me = std::make_shared<EngineGLES>();
            return me;
        }

        EngineGLES::EngineGLES() {}

        std::shared_ptr<Texture> EngineGLES::CreateTexture(EPixel bpp, size_t width, size_t height, void const* data)
        {
            auto texture = std::make_shared<TextureGLES>();
            texture->Generate(bpp, width, height, data);
            __zen_must_else(texture, "failed to create TextureGLES");
            return texture;
        }

        std::shared_ptr<Buffer> EngineGLES::GenerateBuffer()
        {
            auto buffer = std::make_shared<BufferGLES>();
            return buffer;
        }
        std::shared_ptr<IndexBuffer> EngineGLES::GenerateIndexBuffer()
        {
            auto buffer = std::make_shared<IndexBufferGLES>();
            return buffer;
        }

        std::shared_ptr<ProgramUniformColor> EngineGLES::CreateProgramUniformColor(std::string const& vshader, std::string const& fshader)
        {
            return std::make_shared<ProgramUniformColorGLES>(vshader, fshader);
        }

        std::shared_ptr<ProgramUniformColor> EngineGLES::DefaultProgramUniformColor()
        {
            static auto ref = std::make_shared<ProgramUniformColorGLES>(GL::VShaderSrc_aVuM, GL::FShaderSrc_uC);
            return ref;
        }

        void EngineGLES::Initialize()
        {
#if ZEN_OS_WIN
            sEGL = std::make_shared<Zen::GL::Window>(Zen::AppWindows_::Only_()->GetHWindow());
#endif
        }

        void EngineGLES::SetClearColor(Color4f color)
        {
            Zen::GL::Render::SetClearColor(color);
        }

        void EngineGLES::BeginRender()
        {
            Zen::GL::Render::ClearColor();
        }

        void EngineGLES::EndRender()
        {
#if ZEN_OS_WIN
            sEGL->SwapBuffer();
#endif
        }

        void EngineGLES::SetClockwiseAsFront(bool clockwise)
        {
            Zen::GL::Render::SetClockwiseAsFront(clockwise);
        }

        std::shared_ptr<ProgramPointTexture> EngineGLES::CreateProgramPointTexture(std::string const& vshader, std::string const& fshader)
        {
            return std::shared_ptr<ProgramPointTexture>();
        }

        std::shared_ptr<ProgramPointTexture> EngineGLES::DefaultProgramPointTexture()
        {
            static auto ref = std::make_shared<ProgramPointTextureGLES>(GL::VShaderSrc_aVCSuMC_vC_PointTex, GL::FShaderSrc_vC_PointTex);
            return ref;
        }

        std::shared_ptr<ProgramPointTexture> EngineGLES::CreateProgramPointTextureA(std::string const& vshader, std::string const& fshader)
        {
            return std::shared_ptr<ProgramPointTexture>();
        }

        std::shared_ptr<ProgramPointTexture> EngineGLES::DefaultProgramPointTextureA()
        {
            static auto ref = std::make_shared<ProgramPointTextureGLES>(GL::VShaderSrc_aVCSuMC_vC_PointTex, GL::FShaderSrc_vC_PointTexA);
            return ref;
        }

        std::shared_ptr<ProgramTexture> EngineGLES::CreateProgramTextureA(std::string const& vshader, std::string const& fshader)
        {
            return std::make_shared<ProgramTextureGLES>(vshader, fshader);
        }

        std::shared_ptr<ProgramTexture> EngineGLES::DefaultProgramTextureA()
        {
            static auto ref = std::make_shared<ProgramTextureGLES>(GL::VShaderSrc_aVuM_Tex, GL::FShaderSrc_uC_TexA);
            return ref;
        }

        std::shared_ptr<ProgramTexture> EngineGLES::CreateProgramTexture(std::string const& vshader, std::string const& fshader)
        {
            return std::make_shared<ProgramTextureGLES>(vshader, fshader);
        }

        std::shared_ptr<ProgramTexture> EngineGLES::DefaultProgramTexture()
        {
            static auto ref = std::make_shared<ProgramTextureGLES>(GL::VShaderSrc_aVuM_Tex, GL::FShaderSrc_uC_Tex);
            return ref;
        }

        std::shared_ptr<ProgramVaryingColor> EngineGLES::CreateProgramVaryingColor(std::string const& vshader, std::string const& fshader)
        {
            return std::make_shared<ProgramVaryingColorGLES>(vshader, fshader);
        }

        std::shared_ptr<ProgramVaryingColor> EngineGLES::DefaultProgramVaryingColor()
        {
            static auto ref = std::make_shared<ProgramVaryingColorGLES>(GL::VShaderSrc_aVCuMC_vC, GL::FShaderSrc_vC);
            return ref;
        }

        size_t BufferGLES::GetByteCount() const
        {
            return mByteCount;
        }

        void BufferGLES::Initialize(size_t size, void const* data)
        {
            mBuffer.Generate(size, data, Zen::GL::EBufferUsage::StaticDraw);
            mByteCount = size;
        }

        void BufferGLES::Update(size_t offset, size_t subSize, void const* data)
        {
            mBuffer.Update(offset, subSize, data);
        }

        void BufferGLES::Active()
        {
            mBuffer.Perform();
        }

        Zen::GL::ArrayBuffer& BufferGLES::GetGLBuffer()
        {
            return mBuffer;
        }

        size_t IndexBufferGLES::GetCount() const
        {
            return mCount;
        }

        int IndexBufferGLES::GetElementSize() const
        {
            return (int)mElementSize;
        }
        void IndexBufferGLES::Active()
        {
            mBuffer.Perform();
        }

        Zen::GL::EType IndexBufferGLES::GetElementType() const
        {
            switch (mElementSize)
            {
            case 1: return Zen::GL::EType::UByte;
            case 2: return Zen::GL::EType::UShort;
            case 4: return Zen::GL::EType::UInt;
            default: return Zen::GL::EType::None;
            }
        }

        void IndexBufferGLES::Initialize(size_t count, uint32_t const* data)
        {
            mCount = count;
            mElementSize = sizeof(uint32_t);
            mBuffer.Generate(mCount << 2, data, Zen::GL::EBufferUsage::StaticDraw);
        }

        void IndexBufferGLES::Update(size_t offset, size_t subCount, uint32_t const* data)
        {
#if ZEN_DEBUG
            __zen_must_else(offset + subCount <= mCount, "out range.");
#endif
            mBuffer.Update(offset << 2, subCount << 2, data);
        }

        void IndexBufferGLES::Initialize(size_t count, uint16_t const* data)
        {
            mCount = count;
            mElementSize = sizeof(uint16_t);
            mBuffer.Generate(mCount << 1, data, Zen::GL::EBufferUsage::StaticDraw);
        }

        void IndexBufferGLES::Update(size_t offset, size_t subCount, uint16_t const* data)
        {
#if ZEN_DEBUG
            __zen_must_else(offset + subCount <= mCount, "out range.");
#endif
            mBuffer.Update(offset << 1, subCount << 1, data);
        }

        ProgramUniformColorGLES::ProgramUniformColorGLES(std::string const& vshader, std::string const& fshader)
        {
            mKit.BindProgram(vshader, fshader);
        }

        void ProgramUniformColorGLES::Attach(Buffer* vertex, int dimension, Mat4 const* matrix, Color4f const* ucolor)
        {
            using namespace Zen::GL;
            auto _vertexBuffer = dynamic_cast<BufferGLES*>(vertex);
            mKit.Active();
            _vertexBuffer->Active();
            Render::SetVertexBuffer(mKit.GetAVertex(), dimension, EType::Float, false, 0, 0);
            Render::SetUniform(mKit.GetUMatrix(), matrix ? *matrix : sUnitMatrix);

            Render::SetUniform(mKit.GetUColor(), ucolor ? *ucolor : sDefaultUColor);
        }

        ProgramVaryingColorGLES::ProgramVaryingColorGLES(std::string const& vshader, std::string const& fshader)
        {
            mKit.BindProgram(vshader, fshader);
        }

        void ProgramVaryingColorGLES::Attach(Buffer* vertex, int dimension, Mat4 const* matrix, Buffer* vcolor, Color4f const* ucolor)
        {
            using namespace Zen::GL;
            mKit.Active();

            auto _vertexBuffer = dynamic_cast<BufferGLES*>(vertex);
            _vertexBuffer->Active();
            Render::SetVertexBuffer(mKit.GetAVertex(), dimension, EType::Float, false, 0, 0);

            auto _colorsBuffer = dynamic_cast<BufferGLES*>(vcolor);
            _colorsBuffer->Active();
            Render::SetVertexBuffer(mKit.GetAColor(), 4, EType::Float, false, 0, 0);

            Render::SetUniform(mKit.GetUMatrix(), matrix ? *matrix : sUnitMatrix);

            Render::SetUniform(mKit.GetUColor(), ucolor ? *ucolor : sDefaultUColor);
        }

        ProgramTextureGLES::ProgramTextureGLES(std::string const& vshader, std::string const& fshader)
        {
            mKit.BindProgram(vshader, fshader);
        }

        void ProgramTextureGLES::Attach(Buffer* vertex, int dimension, Mat4 const* matrix, Buffer* coords, Texture* texture, SamplerMode const* sampler, Color4f const* ucolor)
        {
            using namespace Zen::GL;

            mKit.Active();

            auto _vertexBuffer = dynamic_cast<BufferGLES*>(vertex);
            _vertexBuffer->Active();
            Render::SetVertexBuffer(mKit.GetAVertex(), dimension, EType::Float, false, 0, 0);

            auto _coordsBuffer = dynamic_cast<BufferGLES*>(coords);
            _coordsBuffer->Active();
            Render::SetVertexBuffer(mKit.GetATexCoord(), 2, EType::Float, false, 0, 0);

            auto _texture = dynamic_cast<TextureGLES*>(texture);
            GL::Sampler samplerGL = { ToGL(sampler->minFilter), ToGL(sampler->magFilter), ToGL(sampler->swrap), ToGL(sampler->twrap) };
            Render::SetTexture(mKit.GetUTex(), 0, _texture->GetTextureId(), samplerGL);

            Render::SetUniform(mKit.GetUMatrix(), matrix ? *matrix : sUnitMatrix);

            Render::SetUniform(mKit.GetUColor(), ucolor ? *ucolor : sDefaultUColor);
        }

        ProgramPointTextureGLES::ProgramPointTextureGLES(std::string const& vshader, std::string const& fshader)
        {
            mKit.BindProgram(vshader, fshader);
        }

        void ProgramPointTextureGLES::Attach(Buffer* vertex, int dimension, Mat4 const* matrix, Buffer* colors, Buffer* sizes, Texture* texture, SamplerMode const* sampler, Color4f const* ucolor)
        {
            using namespace Zen::GL;
            mKit.Active();

            auto _vertexBuffer = dynamic_cast<BufferGLES*>(vertex);
            _vertexBuffer->Active();
            Render::SetVertexBuffer(mKit.GetAVertex(), dimension, EType::Float, false, 0, 0);

            auto _sizeBuffer = dynamic_cast<BufferGLES*>(sizes);
            _sizeBuffer->Active();
            Render::SetVertexBuffer(mKit.GetASize(), 1, EType::Float, false, 0, 0);

            auto _colorsBuffer = dynamic_cast<BufferGLES*>(colors);
            _colorsBuffer->Active();
            Render::SetVertexBuffer(mKit.GetAColor(), 4, EType::Float, false, 0, 0);

            Render::SetUniform(mKit.GetUMatrix(), matrix ? *matrix : sUnitMatrix);

            auto _texture = dynamic_cast<TextureGLES*>(texture);

            GL::Sampler samplerGL = { ToGL(sampler->minFilter), ToGL(sampler->magFilter), ToGL(sampler->swrap), ToGL(sampler->twrap) };
            Render::SetTexture(mKit.GetUTex(), 0, _texture->GetTextureId(), samplerGL);

            Render::SetUniform(mKit.GetUColor(), ucolor ? *ucolor : sDefaultUColor);
        }
    } // namespace Graphics
} // namespace Zen
